package lang4j.generator;

import lang4j.parser.generated.*;
import org.apache.commons.lang.StringUtils;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Iterator;
import java.util.List;

/**
 * User: felix
 * Date: 04.11.2005
 * Time: 20:04:22
 */
public class GrammarGenerator {
    private Lang4jGrammar grammar;
    private Transformer transformer;
    StringWriter writer = new StringWriter();
    PrintWriter out = new PrintWriter(writer);


    public GrammarGenerator(final Lang4jGrammar grammar, final Transformer transformer) {
        this.grammar = grammar;
        this.transformer = transformer;
        render();
    }

    private void render() {
        for (Iterator iter = grammar.getProductions().iterator(); iter.hasNext();) {
            Production production = (Production) iter.next();
            production.accept(new ProductionRenderer());
        }
    }


    public String getProductions() {
        return writer.getBuffer().toString();
    }

    private class ProductionRenderer implements ProductionVisitor, ListProductionVisitor, TypeProductionVisitor {
        public void visitTypeProduction(TypeProduction typeProduction) {
            typeProduction.accept((TypeProductionVisitor) this);
        }

        public void visitListProduction(ListProduction listProduction) {
            listProduction.accept((ListProductionVisitor) this);
        }

        public void visitEnumProduction(EnumProduction enumProduction) {
            String name = enumProduction.getName();
            String className = StringUtils.capitalize(enumProduction.getName());
            out.println(name + " returns[" + className + " " + name + "]");
            out.print("   :");
            boolean first = true;
            for (Iterator iterator = enumProduction.getValues().iterator(); iterator.hasNext();) {
                if (!first) {
                    out.print("  |");
                }
                LabelledTerminal labelledTerminal = (LabelledTerminal) iterator.next();
                labelledTerminal.getTerminal().accept(new TerminalVisitor() {

                    public void visitCharToken(CharToken charToken) {
                        out.print(transformer.getTokens().get(new Character(charToken.getText())));
                    }

                    public void visitKeyword(Keyword keyword) {
                        out.print(transformer.getKeywords().get(keyword.getText()));
                    }
                });
                out.println("{" + name + " = " + className + "." + labelledTerminal.getLabel() + ";}");

                first = false;
            }
            out.println(";");

        }


        public void visitListProductionStar(ListProductionStar listProductionStar) {
            String name = listProductionStar.getName();
            String elementClass = determineElementClass((ListProduction) listProductionStar);
            out.println(name + " returns[java.util.List list]");

            out.println("   @init{");
            out.println("       list=new java.util.ArrayList();");
            out.println("   }");
            out.println("   : (");

            listProductionStar.getType().accept(new ListElementTypeVisitor() {
                public void visitProductionReference(ProductionReference productionReference) {
                    out.println("       listElement=" + productionReference.getProduction().getName());
                    out.println("           {");
                    out.println("               list.add(listElement);");
                    out.println("           }");
                }

                public void visitReferenceElement(ReferenceElement referenceElement) {
                    out.println("       reference=IDENT");
                    out.println("         {");
                    out.println("           list.add(null);");
                    //todo hier unten mal nachgucken ob das jemals lief
                    out.println("           identifierMap.resolveReference(\""+referenceElement.getProduction().getName()+"\",reference.getText(),list,list.size()-1,tokenInfo(reference));");
                    out.println("         }");

                }
            });


            out.println("   )*\n;");


        }

        private String determineElementClass(ListProduction listProduction) {
            return StringUtils.capitalize(listProduction.getType().getProduction().getName());
        }

        public void visitListProductionPow(ListProductionPow listProductionPow) {
                    String name = listProductionPow.getName();
            String elementClass = determineElementClass((ListProduction) listProductionPow);
            out.println(name + " returns[java.util.List list]");

            out.println("   @init{");
            out.println("       list=new java.util.ArrayList();");
            out.println("   }");
            out.println("   : (");

            final String[] ret=new String[1];
            listProductionPow.getLit().accept(new TerminalVisitor() {
                public void visitCharToken(CharToken charToken) {
                    ret[0]= String.valueOf(charToken.getText());
                }

                public void visitKeyword(Keyword keyword) {
                    ret[0]=(keyword.getText());
                }
            });
            final String separator="'" + ret[0] +"'";
            listProductionPow.getType().accept(new ListElementTypeVisitor() {
                public void visitProductionReference(ProductionReference productionReference) {
                    out.println("       firstElement=" + productionReference.getProduction().getName());
                    out.println("           {");
                    out.println("               list.add(firstElement);");
                    out.println("           }");

                    out.println("       ( "+separator+" nextElement=" + productionReference.getProduction().getName());
                    out.println("           {");
                    out.println("               list.add(nextElement);");
                    out.println("           }");
                    out.println("         )*");
                }

                public void visitReferenceElement(ReferenceElement referenceElement) {
                    out.println("       reference=IDENT");
                    out.println("         {");
                    out.println("           list.add(null);");
                    out.println("           identifierMap.resolveReference(\""+referenceElement.getProduction().getName()+"\",reference.getText(),list,list.size()-1,tokenInfo(reference));");
                    out.println("         }");


                    out.println("       ("+separator+" nextReference=IDENT");
                    out.println("         {");
                    out.println("           list.add(null);");
                    out.println("           identifierMap.resolveReference(\""+referenceElement.getProduction().getName()+"\",nextReference.getText(),list,list.size()-1,tokenInfo(nextReference));");
                    out.println("         })*");



                }
            });

            out.println("   )?\n;");
        }

        public void visitInterfaceProduction(InterfaceProduction interfaceProduction) {
            String name = interfaceProduction.getName();
            String className = StringUtils.capitalize(name);
            out.println(name + " returns[" + className + " value]");
            if (interfaceProduction.getKeyField() != null) {
                String keyGetter = StringUtils.capitalize(interfaceProduction.getKeyField());
                out.println("@after{ identifierMap.addInstance(\"" + name + "\",value.get" + keyGetter + "(),value);}");
            }
            out.print("    :(");

            boolean first = true;
            for (Iterator iterator = interfaceProduction.getSubTypes().iterator(); iterator.hasNext();) {
                TypeProduction typeProduction = (TypeProduction) iterator.next();
                if (!first) {
                    out.print("    |");
                }
                first = false;
                out.println(typeProduction.getName() + "Label =" + typeProduction.getName());
                out.println("{value = "+typeProduction.getName() + "Label;}");
            }

            out.println(")");

            out.println(";\n");
        }

        public void visitConstructorProduction(ConstructorProduction constructorProduction) {
            String name = constructorProduction.getName();
            String className = StringUtils.capitalize(name);
            out.println(name + " returns [" + className + " " + name + "]");
            out.println("    @init{");
            out.println("        " + name + " = new " + className + "();");
            out.println("        " + name+  ".mSetStart(new Position(((TokenStream)input).LT(1)));");

            out.println("    }");
            out.println("    :");

            renderProductionFactors(constructorProduction, constructorProduction.getFactors());
            renderInstanceMapPusher(constructorProduction);

            out.println("        {" + name+  ".mSetEnd(new Position(((TokenStream)input).LT(0)));}");

            out.println(";\n");


        }

        private void renderInstanceMapPusher(ConstructorProduction constructorProduction) {
            String keyField = constructorProduction.getKeyField();
            if (keyField == null) return;
            keyField = StringUtils.capitalize(keyField);
            String name = constructorProduction.getName();
            out.println("    {");
            out.println("         identifierMap.addInstance(\"" + name + "\"," + name + ".get" + keyField + "()," + name+ ");");
            out.println("    }");

        }

        private void renderProductionFactors(final ConstructorProduction production,List factors) {
            for (Object factor : factors) {
                ProductionFactor productionFactor = (ProductionFactor) factor;
                productionFactor.accept(new ProductionFactorVisitor() {
                    public void visitTerminal(Terminal terminal) {
                        terminal.accept(new TerminalVisitor() {
                            public void visitCharToken(CharToken charToken) {
                                out.print("'" + charToken.getText() + "' ");
                                //out.print("    "+transformer.getTokens().get(new Character(charToken.getText())));
                            }

                            public void visitKeyword(Keyword keyword) {
                                out.print("'" + keyword.getText() + "' ");
                                ;
                            }
                        });
                    }

                    public void visitAttribute(Attribute attribute) {
                        attribute.accept(new AttributeVisitor() {

                            public void visitDeepAttribute(DeepAttribute deepAttribute) {
                                final String name;
                                name = deepAttribute.hasName() ? StringUtils.capitalize(deepAttribute.getName()) :
                                        StringUtils.capitalize(deepAttribute.getProduction().getName());
                                String capName = StringUtils.capitalize(name);
                                String temp = "temp" + capName;
                                out.println("    " + temp + " = " + deepAttribute.getProduction().getName());
                                out.println("         {" + production.getName() + ".set" + capName + "(" + temp + ");}");
                            }

                            public void visitFlatAttribute(FlatAttribute flatAttribute) {
                                final String name = flatAttribute.getName();
                                final String capName = StringUtils.capitalize(name);
                                flatAttribute.accept(new FlatAttributeVisitor() {

                                    public void visitIntLiteralAttribute(IntLiteralAttribute intLiteralAttribute) {
                                        String temp = name + "IToken";
                                        out.println("    " + temp + "= NUM_INT");
                                        out.println("         {" + production.getName() + ".set" + capName + "(NumberUtils.createInteger(" + temp + ".getText()).intValue());}");
                                    }

                                    public void visitDoubleLiteralAttribute(DoubleLiteralAttribute doubleLiteralAttribute) {
                                        String temp = name + "DToken";
                                        out.println("    " + temp + "= NUM_DOUBLE");
                                        out.println("         {" + production.getName() + ".set" + capName + "(NumberUtils.createDouble(" + temp + ".getText()).doubleValue());}");
                                    }

                                    public void visitCharLiteralAttribute(CharLiteralAttribute charLiteralAttribute) {
                                        String temp = name + "CToken";
                                        out.println("    " + temp + "= CHAR_LITERAL");
                                        out.println("         {" + production.getName() + ".set" + capName + "(StringEscapeUtils.unescapeJava(" + temp + ".getText().substring(1," + temp + ".getText().length()-1)).charAt(0));}");
                                    }

                                    public void visitBooleanLiteralAttribute(BooleanLiteralAttribute booleanLiteralAttribute) {
                                        String temp = "temp" + capName;
                                        out.println("    " + temp + " = booleanLiteral");
                                        out.println("         {" + production.getName() + ".set" + capName + "(" + temp + ");}");

                                    }

                                    public void visitStringLiteralAttribute(StringLiteralAttribute stringLiteralAttribute) {
                                        String temp = name + "Token";
                                        out.println("    " + temp + "= STRING_LITERAL");
                                        out.println("         {" + production.getName() + ".set" + capName + "(StringEscapeUtils.unescapeJava(" + temp + ".getText().substring(1," + temp + ".getText().length()-1)));}");
                                    }

                                    public void visitIdentifierAttribute(IdentifierAttribute identifierAttribute) {
                                        String temp = name + "Token";
                                        out.println("    " + temp + " = IDENT");
                                        out.println("         {" + production.getName() + ".set" + capName + "(" + temp + ".getText());}");
                                    }

                                    public void visitReferenceAttribute(ReferenceAttribute referenceAttribute) {
                                        String refName = referenceAttribute.getProduction().getName();
                                        String temp = name + "Token";
                                        out.println("    " + temp + " = IDENT");
                                        out.println("         {identifierMap.resolveReference(\"" + refName + "\", " + temp + ".getText(), \"" + name + "\", " + production.getName() + ",tokenInfo(" + temp + "));}");
                                    }

                                    public void visitMultiLineStringAttribute(MultiLineStringAttribute multiLineStringAttribute) {
                                        String temp = name + "Token";
                                        out.println("    " + temp + "= MULTI_LINE_STRING");
                                        out.println("         {" + production.getName() + ".set" + capName + "(StringEscapeUtils.unescapeJava(" + temp + ".getText().substring(3," + temp + ".getText().length()-3)));}");
                                    }
                                });
                            }
                        });
                    }

                    public void visitOptionalAttributeList(OptionalAttributeList optionalAttributeList) {
                        out.println("    (");
                        renderProductionFactors(production, optionalAttributeList.getFactors());
                        out.println("    )?");
                    }
                });
            }
        }


    }
}
